package org.dei.perla.sys.registry;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

import org.dei.perla.parser.expressions.Constant;
import org.dei.perla.parser.expressions.ConstantCastException;
import org.dei.perla.parser.expressions.ConstantID;
import org.dei.perla.parser.expressions.Node;
import org.dei.perla.parser.expressions.NodeAND;
import org.dei.perla.parser.expressions.NodeBetween;
import org.dei.perla.parser.expressions.NodeComparisonOperation;
import org.dei.perla.parser.expressions.NodeConstant;
import org.dei.perla.parser.expressions.NodeEqual;
import org.dei.perla.parser.expressions.NodeExists;
import org.dei.perla.parser.expressions.NodeExistsAll;
import org.dei.perla.parser.expressions.NodeGreater;
import org.dei.perla.parser.expressions.NodeLogicalObjectAttribute;
import org.dei.perla.parser.expressions.NodeLogicalOperation;
import org.dei.perla.parser.expressions.NodeMinor;
import org.dei.perla.parser.expressions.NodeNOT;
import org.dei.perla.parser.expressions.NodeOR;
import org.dei.perla.parser.expressions.NodeStrictlyGreater;
import org.dei.perla.parser.expressions.NodeStrictlyMinor;
import org.dei.perla.parser.expressions.NodeXOR;
import org.dei.perla.sys.device.fpc.FunctionalityProxyComponent;

/**
 * Prima implementazione general purpose del registry.
 * I vari FPC sono indicizzati a partire dai valori degli attributi che
 * espongono.
 * 
 * Ogni entry nella attributeMap consiste a sua volta in una mappa utilizzata
 * per indicizzare le FPC a seconda del valore di quel particolare attributo.
 */
public class MapRegistry implements RegistryInterface {
	
	/**
	 * Costante ID, utilizzata per identificare la chiave dell'attributo
	 * ID nella attributeMap
	 */
	private static final String ID = "ID";
	
	/**
	 * Mappa principale degli attributi. Permette di accedere
	 * alle sottomappe che contengono gli attributi indicizzati per valore 
	 */
	private final Map<String, SortedMap<Constant, Set<FunctionalityProxyComponent>>> attributeMap;
	
	/**
	 * Mappa per la memorizzazione degli attributi associati ad
	 * una singola FPC. Viene utilizzata durante l'aggiornamento
	 * della attributeMap (update o eliminazioni di FPC)
	 */
	private final SortedMap<ConstantID, Map<String, Constant>> idMap;
	
	/**
	 * Set contenente tutti i riferimenti alle FPC registrate nel sistema
	 */
	private final Set<FunctionalityProxyComponent> allFpc;
	
	/**
	 * Istanza del registry.
	 */
	private static RegistryInterface instance = null;
	
	/**
	 * Istanza del ConditionEvaluator
	 */
	private final ConditionEvaluator conditionEvaluator;
	
	/**
	 * Costruttore privato.
	 * E' possibile accedere al registry unicamente a partire dal metodo
	 * RegistryImplementation.getInstance()
	 */
	private MapRegistry() {
		// Inizializzazione mappa attributi
		attributeMap = Collections.synchronizedMap(
				new HashMap<String, SortedMap<Constant, Set<FunctionalityProxyComponent>>>());	
		// Inizializzazione e collegamento mappa degli ID (l'ID e' l'unico
		// campo che e' sicuramente in comune a tutti i nodi)
		attributeMap.put(ID, Collections.synchronizedSortedMap(
				new TreeMap<Constant, Set<FunctionalityProxyComponent>>(ConstantComparator.getInstance())));
		// Inizializzazione mappa degli id
		idMap = Collections.synchronizedSortedMap(
				new TreeMap<ConstantID, Map<String, Constant>>(ConstantComparator.getInstance()));
		// Inizializzazione insieme di tutte le FPC registrate nel sistema
		allFpc = new HashSet<FunctionalityProxyComponent>();
		// Inizializzazione del ConditionEvaluator.
		conditionEvaluator = new ConditionEvaluator();
	}
	
	/**
	 * Metodo per la restituzione della singola istanza del registry
	 * presente nel sistema.
	 * @return istanza del registry
	 */
	public static RegistryInterface getInstance() {
		if (instance == null) {
			instance = new MapRegistry();
		}
		return instance;
	}

	/**
	 * Inserisce una nuova FPC all'interno delle strutture dati del registry.
	 */
	public synchronized void registerFpc(ConstantID id, Map<String, Constant> values, FunctionalityProxyComponent fpc) {
		// L'inserimento avviene esclusivamente se l'FPC non e' gia' stata registrata in precedenza
		// e se l'oggetto fpc passato come parametro non e' null
		if (!idMap.containsKey(id) && fpc != null) {
			// Rimuovo, per precauzione, l'attributo ID che potrebbe essere stato
			// inserito per errore nella mappa values.
			// Questa funzione non va in errore se ID non e' una chiave valida
			// della mappa
			values.remove(ID);
			// Aggiungo l'attributo ID alle strutture dati del registry
			idMap.put(id, values);
			addFPCAttribute(ID, id, fpc);
			// Aggiungo l'FPC all'insieme contenente tutte le FPC registrate
			allFpc.add(fpc);
			// Controllo ogni elemento nella mappa values e lo aggiungo alla
			// lista degli attributi
			for (Map.Entry<String, Constant> entry : values.entrySet()) {
				addFPCAttribute(entry.getKey(), entry.getValue(), fpc);
			}
		}
	}
	
	/**
	 * Inserisce una nuova FPC all'interno delle strutture dati del registry.
	 * L'id associato all'FPC viene generato automaticamente dal sistema
	 */
	public synchronized ConstantID registerFpc(Map<String, Constant> values, FunctionalityProxyComponent fpc) {
		ConstantID id = getFreeID();
		registerFpc(id, values, fpc);
		return id;
	}
	
	/**
	 * Crea un nuovo identificativo univoco. E' importante garantire che il
	 * metodo che chiama questa funzione sia opportunamente sincronizzato
	 * per evitare che una chiamata concorrente possa essere eseguita dopo
	 * la richiesta dell'ID ma prima del suo utilizzo.
	 * @return un nuovo identificativo univoco
	 */
	private synchronized ConstantID getFreeID() {
		try {
			// Recupero l'array di byte corrispondente al valore dell'ID massimo
			byte[] highestID = attributeMap.get(ID).lastKey().getValueString().getBytes();
			// Incremento l'ultimo carattere
			highestID[highestID.length-1]++;
			return new ConstantID(new String(highestID));
		} catch (ConstantCastException e) {
			// Se gli inserimenti nelle strutture dati vengono eseguiti
			// correttamente non si arrivera' mai in questa situazione,
			// visto che tutte le costanti memorizzate nella sottomappa
			// ID sono di tipo ConstantID
			return null;
		}
	}
	
	/**
	 * Metodo per l'aggiunta di un attributo nella attributeMap
	 * @param attributeName nome dell'attributo da inserire nella mappa
	 * @param constant valore della costante da inserire
	 * @param fpc FPC da associare all'attributo
	 */
	private void addFPCAttribute(String attributeName, Constant constant, FunctionalityProxyComponent fpc) {
		if (attributeMap.containsKey(attributeName)) {
			// La mappa contiene l'attributo di cui ho bisogno, recupero
			// la sottomappa corrispondente
			SortedMap<Constant, Set<FunctionalityProxyComponent>> fpcMap = attributeMap.get(attributeName);
			
			if (fpcMap.containsKey(constant)) {
				// Esiste gia' una entry nella sottomappa con lo
				// stesso valore per l'attributo che voglio inserire
				Set<FunctionalityProxyComponent> fpcSet = fpcMap.get(constant);
				fpcSet.add(fpc);
				
			} else {
				// Non esiste un'entry nella sottomappa con lo
				// stesso valore per l'attributo che voglio
				// inserire, ne creo una nuova
				Set<FunctionalityProxyComponent> fpcSet = new HashSet<FunctionalityProxyComponent>();
				fpcSet.add(fpc);
				fpcMap.put(constant, fpcSet);
			}
			
		} else {
			// La mappa non contiene l'attributo di cui ho bisogno,
			// creo una nuova TreeMap che lo conterra'
			SortedMap<Constant, Set<FunctionalityProxyComponent>> fpcMap = 
				new TreeMap<Constant, Set<FunctionalityProxyComponent>>(ConstantComparator.getInstance());
			Set<FunctionalityProxyComponent> fpcSet = new HashSet<FunctionalityProxyComponent>();
			fpcSet.add(fpc);
			fpcMap.put(constant, fpcSet);
			attributeMap.put(attributeName, fpcMap);
		}
	}

	/**
	 * Elimina una FPC dalle strutture dati di memorizzazione interne
	 */
	public synchronized void unregisterFpc(ConstantID id) {
		// Recupero l'oggetto FPC da rimuovere
		FunctionalityProxyComponent fpc = getFPC(id);
		if (fpc != null) {
			// Recupero la lista di attributi associati all'FPC da eliminare
			for (Map.Entry<String, Constant> fpcAttribute : idMap.get(id).entrySet()) {
				removeFpcAttribute(fpcAttribute.getKey(), fpcAttribute.getValue(), fpc);
			}
			// Rimuovo l'attributo ID dalla attributeMap esplicitamente,
			// dato che non e' contenuto nella lista memorizzata in idMap
			removeFpcAttribute(ID, id, fpc);
			// Rimuovo l'entry nella idMap
			idMap.remove(id);
			// Rimuovo il riferimento all'FPC dalla lista di tutte le FPC
			allFpc.remove(fpc);
		}
	}
	
	/**
	 * Restituisce l'FPC che ha associato l'id passato come parametro
	 * @param id valore per la ricerca dell'FPC
	 * @return FPC FPC corrispondente all'id passato come parametro. Nel caso
	 * la struttura non contenga l'id specificato o ci sia stato un errore durante
	 * gli inserimenti viene restituito null 
	 */
	private FunctionalityProxyComponent getFPC(ConstantID id) {
		Set<FunctionalityProxyComponent> fpcSet = attributeMap.get(ID).get(id);
		// Se il set che recupero e' nullo allora
		// non e' stata registrata alcuna FPC con l'ID
		// specificato. Ritorno null anche nel caso le 2
		// strutture dati principali siano disallineate
		if (fpcSet == null || !idMap.containsKey(id)) {
			return null;
		}
		Iterator<FunctionalityProxyComponent> iterator = fpcSet.iterator();
		if (iterator.hasNext()) {
			// Per come i valori sono stati inseriti nelle strutture dati
			// del registry sono sicuro che ci sia esattamente una FPC
			// registrata per ogni valore di ConstantID
			return iterator.next();
		} else {
			// Se le strutture dati del registry sono trattate correttamente
			// non si arriva mai in questa situazione
			return null;
		}
 	}
	
	/**
	 * Rimuove un FPC da un particolare attributo nella attributeMap
	 * @param attributeName attributo da eliminare
	 * @param attributeValue valore dell'attributo da eliminare
	 * @param fpc FPC da eliminare per quell'attributo
	 */
	private void removeFpcAttribute(String attributeName, Constant attributeValue, FunctionalityProxyComponent fpc) {
		Set<FunctionalityProxyComponent> fpcSet = attributeMap.get(attributeName).get(attributeValue);
		if (fpcSet != null) {
			if (fpcSet.size() == 1) {
				// Elimino l'intera entry nella lista degli attributi se
				// l'FPC da togliere e' l'unica nel set che sto considerando
				attributeMap.get(attributeName).remove(attributeValue);
				// Nel caso la mappa attributi non contenga alcun altro
				// set di FPC associato al nome dell'attributo passato
				// come parametro rimuovo completamente l'entry
				if (attributeMap.get(attributeName).isEmpty()) {
					attributeMap.remove(attributeName);
				}
			} else {
				// Altrimenti mi limito a togliere la singola FPC
				// e lascio il resto delle strutture dati invariate
				fpcSet.remove(fpc);
			}
		}
	}

	/**
	 * Aggiorna i valori per una FPC
	 */
	public synchronized void updateFpc(ConstantID id, Map<String, Constant> values) {
		// Recupero l'oggetto FPC
		FunctionalityProxyComponent fpc = getFPC(id);
		if (fpc != null) {
			Constant newValue;
			Comparator<Constant> comparator = ConstantComparator.getInstance();
			// Eseguo una copia dei dati memorizzati in idMap per la
			// determinata FPC in modo tale che possa modificare i valori
			// in essa contenuti senza invalidare l'iteratore
			Map<String, Constant> attributeCopy = new HashMap<String, Constant>(idMap.get(id));
			// Controllo ogni valore precedentemente memorizzato nelle
			// strutture dati con quelli passati come parametro al metodo.
			// Tutti gli attributi nella struttura values che non sono stati
			// specificati durante la registrazione dell'FPC saranno ignorati
			for (Map.Entry<String, Constant> entry : attributeCopy.entrySet()) {
				newValue = values.get(entry.getKey());
				// Nel caso un attributo non sia stato inserito all'interno
				// della struttura values assumo che non ci sia stato alcun
				// cambiamento
				if (newValue != null) {
					// Eseguo le modifiche nelle strutture dati solamente se i
					// nuovi valori sono effettivamente diversi da quelli
					// memorizzati in precedenza
					if (comparator.compare(newValue, entry.getValue()) != 0) {
						// Modifico il valore memorizzato nella attributeMap
						removeFpcAttribute(entry.getKey(), entry.getValue(), fpc);
						addFPCAttribute(entry.getKey(), newValue, fpc);
						// Modifico il valore memorizzato nella idMap
						idMap.get(id).remove(entry.getKey());
						idMap.get(id).put(entry.getKey(), newValue);
					}
				}
			}
		}
	}
	
	/**
	 * Restituisce gli oggetti FPC che hanno quei determinati attributi
	 */
	public Set<FunctionalityProxyComponent> FpcByAttributeName(Set<String> attributeNames, boolean intersection) {
		// Nel caso l'utente non inserisca alcun parametro di ricerca
		// restituisco un set vuoto (una FPC senza alcun attributo
		// non ha alcun significato, quindi dovremmo entrare nel ramo then
		// dell'if solo in caso di errori)
		if (attributeNames.size() == 0) {
			return new HashSet<FunctionalityProxyComponent>();
		}
		
		List<Set<FunctionalityProxyComponent>> fpcSetList = 
			new ArrayList<Set<FunctionalityProxyComponent>>();
		synchronized(this) {
			// Aggiungo nella variabile temporanea fpcSetList tutte le FPC
			// da elaborare, divise per attributo
			for (String name : attributeNames) {
				Set<FunctionalityProxyComponent> fpcSet = new HashSet<FunctionalityProxyComponent>();
				// Nel caso attributeMap contenga l'attributo procedo
				// con il recupero di tutte le FPC corrispondenti e le aggiungo
				// nella lista fpcSetList. In caso contrario aggiungero' un set
				// vuoto
				if (attributeMap.containsKey(name)) {
					for (Map.Entry<Constant, Set<FunctionalityProxyComponent>> entry : attributeMap.get(name).entrySet()) {
						fpcSet.addAll(entry.getValue());
					}
				}
				fpcSetList.add(fpcSet);
			}
		}
		
		// A seconda del valore indicato durante la chiamata eseguo
		// l'unione o l'intersezione dei valori recuperati nella 
		// fase precedente
		if (intersection) {
			return computeIntersection(fpcSetList);
		} else {
			return computeUnion(fpcSetList);
		}
	}
	
	/**
	 * Esegue l'intersezione di tutti i set contenuti nella lista passata come
	 * parametro
	 * @param fpcSetList lista di set di cui bisogna computare l'intersezione
	 * @return Set corrispondente all'intersezione dei valori passati come
	 * parametro
	 */
	private Set<FunctionalityProxyComponent> computeIntersection(List<Set<FunctionalityProxyComponent>> fpcSetList) {
		Set<FunctionalityProxyComponent> fpcSet = fpcSetList.get(0);
		fpcSetList.remove(0);
		for (Set<FunctionalityProxyComponent> set : fpcSetList) {
			fpcSet.retainAll(set);
		}
		return fpcSet;
	}
	
	/**
	 * Esegue l'unione di tutti i set contenuti nella lista passata come
	 * parametro
	 * @param fpcSetList lista di set di cui bisogna computare l'unione
	 * @return Set corrispondente all'unione dei valori passati come parametro
	 */
	private Set<FunctionalityProxyComponent> computeUnion(List<Set<FunctionalityProxyComponent>> fpcSetList) {
		Set<FunctionalityProxyComponent> fpcSet = new HashSet<FunctionalityProxyComponent>();
		for (Set<FunctionalityProxyComponent> set : fpcSetList) {
			fpcSet.addAll(set);
		}
		return fpcSet;
	}
	
	/** 
	 * Esecuzione della clausola EXECUTE IF per recuperare le FPC su cui
	 * la query andra' eseguita 
	 */
	public synchronized Set<FunctionalityProxyComponent> query(Node executeIf) {
		return conditionEvaluator.evaluateCondition(executeIf);
	}
	
	@Override
	public synchronized String toString() {
		StringBuffer buffer = new StringBuffer();
		
		buffer.append("FPC attributes:\n");
		// Stampo, per ogni FPC registrata nel sistema, il suo ID e la 
		// lista degli attributi a lei associati
		for (Map.Entry<ConstantID, Map<String, Constant>> fpcId : idMap.entrySet()) {
			buffer.append("FPC ID: " + fpcId.getKey() + "\n");
			for (Map.Entry<String, Constant> fpcAttribute : fpcId.getValue().entrySet()) {
				buffer.append("\t" + fpcAttribute.getKey() + " = " + 
						fpcAttribute.getValue().toString() 
						+ " (" + fpcAttribute.getValue().getClass() + ")\n");
			}
			buffer.append("\n");
		}
		
		buffer.append("\n\nAttribute synopsis\n");
		// Scorro tutti gli attributi memorizzati nella mappa principale
		// (attributeMap)
		for (Map.Entry<String, SortedMap<Constant, Set<FunctionalityProxyComponent>>> attributeEntry : attributeMap.entrySet()) {
			buffer.append(attributeEntry.getKey() + ": \n");
			
			// Scorro tutti i valori memorizzati per l'attributo
			for (Map.Entry<Constant, Set<FunctionalityProxyComponent>> fpcMap : attributeEntry.getValue().entrySet()) {
				buffer.append("\t" + fpcMap.getKey().toString() + 
						" (" + fpcMap.getKey().getClass().toString() + 
						") = Number of registered FPCs: " + fpcMap.getValue().size() + "\n");
			}
		}
		
		return buffer.toString();
	}
	
	/**
	 * Inner class per la valutazione della condizione EXECUTE IF
	 */
	private class ConditionEvaluator {
		
		/**
		 * Valuta l'espressione passata come parametro al fine di determinare
		 * quali FPC. Al e' stato implementato solo un sottoinsieme di tutte le
		 * funzioni necessarie, quindi l'espressivita' del sistema di query del
		 * registry e' limitata. Consultare i javadoc presenti nei metodi della
		 * classe ConditionEvaluator per saperne i limiti.
		 * @param condition espressione da valutare al fine di definire quali
		 * FPC dovranno eseguire una determinata query
		 * @return insieme di FPC che rispettano le condizioni poste
		 * dall'espressione passata come parametro
		 */
		public Set<FunctionalityProxyComponent> evaluateCondition(Node condition) {
			Set<FunctionalityProxyComponent> fpcSet = new HashSet<FunctionalityProxyComponent>();
			// Controllo il tipo di nodo passato come parametro
			// ed invoco il metodo corrispondente
			if (condition instanceof NodeExists) {
				fpcSet.addAll(evaluateExists((NodeExists)condition));
			} else if (condition instanceof NodeLogicalOperation) {
				fpcSet.addAll(evaluateLogicalOperation((NodeLogicalOperation)condition));
			} else if (condition instanceof NodeComparisonOperation) {
				fpcSet.addAll(evaluateComparisonOperation((NodeComparisonOperation)condition));
			}
			return fpcSet;
		}
		
		/**
		 * Valutazione di un'operazione logica (AND, OR, XOR, NOT, ecc).
		 * @param node nodo corrispondente all'operazione logica da valutare.
		 * @return insieme di FPC che soddisfano la porzione di clausola
		 * EXECUTE IF passata come parametro 
		 */
		private Set<FunctionalityProxyComponent> evaluateLogicalOperation(NodeLogicalOperation node) {
			Set<FunctionalityProxyComponent> fpcSet = new HashSet<FunctionalityProxyComponent>();
			// Controllo quale metodo invocare a seconda dell'operazione da
			// eseguire
			if (node instanceof NodeAND) {
				fpcSet.addAll(evaluateAND((NodeAND)node));
			} else if (node instanceof NodeOR) {
				fpcSet.addAll(evaluateOR((NodeOR)node));
			} else if (node instanceof NodeXOR) {
				fpcSet.addAll(evaluateXOR((NodeXOR)node));
			} else if (node instanceof NodeNOT) {
				fpcSet.addAll(evaluateNOT((NodeNOT)node));
			} else if (node instanceof NodeBetween) {
				fpcSet.addAll(evaluateBetween((NodeBetween)node));
			}
			return fpcSet;
		}
		
		/**
		 * Metodo per il calcolo dell'insieme di FPC relativo all'operazione
		 * logica AND.
		 * @param node clausola AND da valutare
		 * @return insieme di FPC che soddisfano la clausola AND porzione della
		 * clausola EXECUTE IF
		 */
		private Set<FunctionalityProxyComponent> evaluateAND(NodeAND node) {
			// Recupero i Set di FPC relativi ai 2 operandi
			Set<FunctionalityProxyComponent> firstNodeFpcs = evaluateCondition(node.getFirstNode());
			Set<FunctionalityProxyComponent> secondNodeFpcs = evaluateCondition(node.getSecondNode());
			// Eseguo l'AND
			firstNodeFpcs.retainAll(secondNodeFpcs);
			return firstNodeFpcs;
		}
		
		/**
		 * Metodo per il calcolo dell'insieme di FPC relativo all'operazione
		 * logica OR.
		 * @param node clausola OR da valutare
		 * @return insieme di FPC che soddisfano la clausola OR porzione della
		 * clausola EXECUTE IF
		 */
		private Set<FunctionalityProxyComponent> evaluateOR(NodeOR node) {
			// Recupero i Set di FPC relativi ai 2 operandi
			Set<FunctionalityProxyComponent> firstNodeFpcs = evaluateCondition(node.getFirstNode());
			Set<FunctionalityProxyComponent> secondNodeFpcs = evaluateCondition(node.getSecondNode());
			// Eseguo l'OR
			firstNodeFpcs.addAll(secondNodeFpcs);
			return firstNodeFpcs;
		}
		
		/**
		 * Metodo per il calcolo dell'insieme di FPC relativo all'operazione
		 * logica XOR.
		 * @param node clausola XOR da valutare
		 * @return insieme di FPC che soddisfano la clausola XOR porzione della
		 * clausola EXECUTE IF
		 */
		private Set<FunctionalityProxyComponent> evaluateXOR(NodeXOR node) {
			// Recupero i Set di FPC relativi ai 2 operandi
			Set<FunctionalityProxyComponent> firstNodeFpcs = evaluateCondition(node.getFirstNode());
			Set<FunctionalityProxyComponent> secondNodeFpcs = evaluateCondition(node.getSecondNode());
			// Eseguo l'XOR
			Set<FunctionalityProxyComponent> xor = new HashSet<FunctionalityProxyComponent>();
			xor.addAll(firstNodeFpcs);
			xor.addAll(secondNodeFpcs);
			firstNodeFpcs.retainAll(secondNodeFpcs); // firstNodes contiene ora l'intersezione
			xor.removeAll(firstNodeFpcs);
			return xor;
		}
		
		/**
		 * Metodo per il calcolo dell'insieme di FPC relativo all'operazione
		 * logica NOT.
		 * @param node clausola NOT da valutare
		 * @return insieme di FPC che soddisfano la clausola NOT porzione della
		 * clausola EXECUTE IF
		 */
		private Set<FunctionalityProxyComponent> evaluateNOT(NodeNOT node) {
			Set<FunctionalityProxyComponent> not = new HashSet<FunctionalityProxyComponent>(allFpc);
			not.removeAll(evaluateCondition(node.getFirstNode()));
			return not;
		}
		
		/**
		 * Metodo per il calcolo dell'insieme di FPC che soddisfano una
		 * clausola BETWEEN.
		 * @param node clausola BETWEEN da valutare
		 * @return insieme di FPC che soddisfano la clausola BETWEEN
		 * porzione della clausola EXECUTE IF
		 */
		private Set<FunctionalityProxyComponent> evaluateBetween(NodeBetween node) {
			Set<FunctionalityProxyComponent> fpcSet = new HashSet<FunctionalityProxyComponent>();
			// Al momento la computazione puo' avvenire solamente se il primo
			// elemento del nodo BETWEEN e' un semplice attributo di un FPC.
			// Questa funzione andrebbe espansa per considerare anche gli altri
			// comportamenti possibili
			if (node.getFirstNode() instanceof NodeLogicalObjectAttribute) {
				// Recupero il nome dell'oggetto logico
				NodeLogicalObjectAttribute attribute = (NodeLogicalObjectAttribute)node.getFirstNode();
				String attributeName = attribute.getLogicalObjectAttributeName();
				SortedMap<Constant, Set<FunctionalityProxyComponent>> attributeValues = attributeMap.get(attributeName);
				// Controllo che la mappa contenga effettivamente l'attributo
				// richiesto
				if (attributeValues != null) {
					// I prossimi passi sono eseguiti esclusivamente se
					// i boundary del between sono delle semplici costanti.
					// Questo caso andrebbe espanso per considerare anche gli
					// altri comportamenti possibili
					if (node.getSecondNode() instanceof NodeConstant &&
							node.getThirdNode() instanceof NodeConstant) {
						Constant lowerBound = ((NodeConstant)node.getSecondNode()).getResult(); 
						Constant upperBound = ((NodeConstant)node.getThirdNode()).getResult();
						// Estraggo i nodi che rispettano i vincoli posti dal
						// BETWEEN
						Map<Constant, Set<FunctionalityProxyComponent>> fpcs = 
							attributeValues.subMap(lowerBound, upperBound);
						// Popolo il set da restituire con le FPC trovate
						for (Map.Entry<Constant, Set<FunctionalityProxyComponent>> entry : fpcs.entrySet()) {
							fpcSet.addAll(entry.getValue());
						}
						// Aggiungo, nel caso esitsta, l'attribute boundary
						// (l'upperbound viene infatti escluso da subMap)
						if (attributeValues.containsKey(upperBound)) {
							fpcSet.addAll(attributeValues.get(upperBound));
						}
					}
				}
			}
			return fpcSet;
		}
		
		/**
		 * Valutazione del nodo Exists. NOTA: al momento non viene gestita la
		 * keyword ALL
		 * @param node nodo da valutare
		 * @return insieme di FPC che posseggono l'attributo definito dal
		 * nodo NodeExists
		 */
		private Set<FunctionalityProxyComponent> evaluateExists(NodeExists node) {
			Set<FunctionalityProxyComponent> fpcSet = new HashSet<FunctionalityProxyComponent>();
			if (node instanceof NodeExistsAll) {
				// Il caso EXIST ALL non viene gestito al momento,
				// restituisco un set vuoto
				// TODO: Implementare EXIST ALL. Potrebbe valere la pena di
				// implementare la clausola EXIST ALL a livello del parser,
				// visto che si tratta esclusivamente di sintactic sugar.
				// La EXIST ALL puo' infatti essere implementata per mezzo
				// della normale clausola exist
				return fpcSet; 
			}
			// Recupero il nome dell'attributo memorizzato all'interno
			// dell'oggetto NodeExists
			NodeLogicalObjectAttribute attribute = (NodeLogicalObjectAttribute)node.getFirstNode();
			String attributeName = attribute.getLogicalObjectAttributeName();
			// Controllo l'esistenza dell'attributo tra le FPC registrate
			// e compilo la lista da restituire al chiamante
			if (attributeMap.containsKey(attributeName)) {
				for (Map.Entry<Constant, Set<FunctionalityProxyComponent>> entry : attributeMap.get(attributeName).entrySet()) {
					fpcSet.addAll(entry.getValue());
				}
			}
			return fpcSet;
		}
		
		/**
		 * Metodo per la valutazione dei nodi di confronto (=, <=, <, =>, >)
		 * @param node nodo da valutare
		 * @return insieme di FPC i cui attributi soddisfano il confronto
		 */
		private Set<FunctionalityProxyComponent> evaluateComparisonOperation(NodeComparisonOperation node) {
			Set<FunctionalityProxyComponent> fpcSet = new HashSet<FunctionalityProxyComponent>();
			String attributeName;
			Constant value;
			// Al momento questo metodo considera solo il caso in cui
			// l'uguaglianza ha come operandi un attributo ed una costante.
			// Il metodo andrebbe espanso per considerare anche gli altri casi
			// in cui ci si potrebbe trovare
			if (node.getFirstNode() instanceof NodeLogicalObjectAttribute && 
					node.getSecondNode() instanceof NodeConstant) {
				// Estraggo gli operandi dal NodeEqual
				attributeName = ((NodeLogicalObjectAttribute)node.getFirstNode()).getLogicalObjectAttributeName();
				value = ((NodeConstant)node.getSecondNode()).getResult();
				// Invoco la vera valutazione della funzione
				fpcSet.addAll(invokeComparisonOperation(node, attributeName, value));
			} else if (node.getFirstNode() instanceof NodeConstant &&
					node.getSecondNode() instanceof NodeLogicalObjectAttribute){
				// Estraggo gli operandi dal NodeEqual
				value = ((NodeConstant)node.getFirstNode()).getResult();
				attributeName = ((NodeLogicalObjectAttribute)node.getSecondNode()).getLogicalObjectAttributeName();
				// Invoco la vera valutazione della funzione
				fpcSet.addAll(invokeComparisonOperation(node, attributeName, value));
			}
			return fpcSet;
		}
		
		/**
		 * Invoca la corretta operazione necessaria per calcolare
		 * l'insieme di FPC richiesto dal confronto
		 * @param node operazione di confronto da eseguire
		 * @param attributeName nome dell'attributo (operando del confronto)
		 * @param value valore con cui confrontare l'attributo
		 * @return insieme di FPC che soddisfano il confronto
		 */
		private Set<FunctionalityProxyComponent> invokeComparisonOperation(NodeComparisonOperation node, 
				String attributeName, Constant value) {
			Set<FunctionalityProxyComponent> fpcSet = new HashSet<FunctionalityProxyComponent>();
			// Analizzo il tipo di nodo e chiamo il metodo corrispondente
			if (node instanceof NodeEqual) {
				fpcSet.addAll(evaluateEqual(attributeName, value));
			} if (node instanceof NodeMinor) {
				fpcSet.addAll(evaluateMinor(attributeName, value, false));
			} if (node instanceof NodeStrictlyMinor) {
				fpcSet.addAll(evaluateMinor(attributeName, value, true));
			} if (node instanceof NodeGreater) {
				fpcSet.addAll(evaluateGreater(attributeName, value, false));
			} if (node instanceof NodeStrictlyGreater) {
				fpcSet.addAll(evaluateGreater(attributeName, value, true));
			}
			return fpcSet;
		}
		
		/**
		 * Metodo per il recupero delle FPC il cui attributo specificato
		 * rispetta la relazione attributeName < value oppure
		 * attributeName <= value
		 * @param attributeName nome dell'attributo su cui eseguire
		 * la selezione
		 * @param value valore dell'estremo destro dell'intervallo
		 * da selezionare
		 * @param strictly se posto a true verra' restituito un insieme di FPC
		 * il cui valore dell'attributo specificato e' strettamente minore del
		 * valore passato come parametro. Se false, verra' invece restituito
		 * l'insieme di FPC relativo ad una valutazione minore non stretta 
		 * @return insieme di FPC che soddisfano la condizione strictly
		 * minor specificata
		 */
		private Set<FunctionalityProxyComponent> evaluateMinor(String attributeName, Constant value, boolean strictly) {
			Set<FunctionalityProxyComponent> fpcSet = new HashSet<FunctionalityProxyComponent>();
			// Recupero la porzione di registry che contiene il mapping
			// con l'attributo che mi interessa
			SortedMap<Constant, Set<FunctionalityProxyComponent>> attributeValues = attributeMap.get(attributeName);
			// Controllo la presenza dell'attributo nella mappa
			if (attributeValues != null) {
				// Utilizzo il metodo subMap per recuperare la porzione di mappa
				// che contiene i valori necessari per soddisfare la query 
				// dell'utente ed aggiungo i valori all'insieme che sara'
				// fatto tornare come parametro
				Constant lowest = attributeValues.firstKey();
				for(Map.Entry<Constant, Set<FunctionalityProxyComponent>> entry : 
					attributeValues.subMap(lowest, value).entrySet()) {
					fpcSet.addAll(entry.getValue());
				}
				// Nel caso non si sia richiesto un intervallo strettamente
				// minore aggiungo anche il valore boundary dell'intervallo
				if (!strictly) {
					// Aggiungo, nel caso sia contenuto nella mappa,
					// il valore boundary (subMap realizza un intervallo
					// il cui estremo destro e' aperto).
					if (attributeValues.containsKey(value)) {
						fpcSet.addAll(attributeValues.get(value));
					}
				}
			}
			return fpcSet;
		}
		
		/**
		 * Metodo per il recupero delle FPC il cui attributo specificato
		 * rispetta la relazione attributeName > value oppure
		 * attributeName => value
		 * @param attributeName nome dell'attributo su cui eseguire
		 * la selezione
		 * @param value valore dell'estremo sinistro dell'intervallo
		 * da selezionare
		 * @param strictly se posto a true verra' restituito un insieme di FPC
		 * il cui valore dell'attributo specificato e' strettamente maggiore del
		 * valore passato come parametro. Se false, verra' invece restituito
		 * l'insieme di FPC relativo ad una valutazione maggiore non stretta 
		 * @return insieme di FPC che soddisfano la condizione strictly
		 * major specificata
		 */
		private Set<FunctionalityProxyComponent> evaluateGreater(String attributeName, Constant value, boolean strictly) {
			Set<FunctionalityProxyComponent> fpcSet = new HashSet<FunctionalityProxyComponent>();
			// Recupero la porzione di registry che contiene il mapping
			// con l'attributo che mi interessa
			SortedMap<Constant, Set<FunctionalityProxyComponent>> attributeValues = attributeMap.get(attributeName);
			// Controllo la presenza dell'attributo nella mappa
			if (attributeValues != null) {
				// Utilizzo il metodo subMap per recuperare la porzione di mappa
				// che contiene i valori necessari per soddisfare la query 
				// dell'utente ed aggiungo i valori all'insieme che sara'
				// fatto tornare come parametro
				Constant highest = attributeValues.lastKey();
				for(Map.Entry<Constant, Set<FunctionalityProxyComponent>> entry : 
					attributeValues.subMap(value, highest).entrySet()) {
					fpcSet.addAll(entry.getValue());
				}
				// Aggiungo anche i valori corrispondenti all'estremo
				// destro dell'intervallo, dato che subMap realizza
				// un'intervallo aperto a destra
				fpcSet.addAll(attributeValues.get(highest));
				// Nel caso si sia richiesto un intervallo strettamente
				// maggiore rimuovo il valore boundary sinistro dell'intervallo
				if (strictly) {
					// Rimuovo, nel caso sia contenuto nella mappa,
					// il valore boundary (subMap realizza un intervallo
					// il cui estremo sinistro e' chiuso).
					if (attributeValues.containsKey(value)) {
						fpcSet.removeAll(attributeValues.get(value));
					}
				}
			}
			return fpcSet;
		}
		
		/**
		 * Metodo per il recupero delle FPC che hanno un attributo pari
		 * ad un valore specificato
		 * @param attributeName nome dell'attributo richiesto
		 * @param value valore che l'attributo deve possedere
		 * @return insieme di FPC il cui attributo richiesto ha valore value
		 */
		private Set<FunctionalityProxyComponent> evaluateEqual(String attributeName, Constant value) {
			Set<FunctionalityProxyComponent> fpcSet = new HashSet<FunctionalityProxyComponent>();
			// Recupero la porzione di registry che contiene il mapping
			// con l'attributo che mi interessa
			Map<Constant, Set<FunctionalityProxyComponent>> attributeValues = attributeMap.get(attributeName);
			// Controllo la presenza dell'attributo nella mappa
			if (attributeValues != null) {
				// Se la mappa contiene il valore che sto cercando
				// allora inserisco le FPC corrispondenti nell'insieme
				// di ritorno
				if (attributeValues.containsKey(value)) {
					fpcSet.addAll(attributeValues.get(value));
				}
			}
			return fpcSet;
		}
	}
}
